package online.filter;

import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import online.filter.helper.DuplicateBodyResponse;
import online.filter.helper.DuplicateHeaderWrapper;
import online.filter.helper.InterceptResponseWrapper;
import online.listener.SessionMutexListener;

import org.apache.logging.log4j.LogManager;

import core.config.Factory;
import core.util.NumberUtil;

/**
 * REST化フィルタ
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class RestizeFilter implements Filter {

	/** クラス名 */
	private static final String CLAZZ = RestizeFilter.class.getName();
	/** クエリ文字列 */
	private static final String REDIRECT_URL = CLAZZ + ".REDIRECT_URL";
	/** リダイレクトなし */
	private static final String NO_REDIRECT = CLAZZ + ".NO_REDIRECT";

	/** アクションセション最大数 */
	private int max = 3;

	/**
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
		String val = filterConfig.getInitParameter("max");
		if (!Objects.toString(val, "").isEmpty()) {
			this.max = NumberUtil.toInt(val, 3);
		}
	}

	/**
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
		return;
	}

	/**
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 * javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@Override
	public void doFilter(final ServletRequest svRequest, final ServletResponse svResponse,
			final FilterChain chain) throws IOException, ServletException {
		if (HttpServletRequest.class.isInstance(svRequest)
				&& HttpServletResponse.class.isInstance(svResponse)) {
			HttpServletRequest request = HttpServletRequest.class.cast(svRequest);
			HttpServletResponse response = HttpServletResponse.class.cast(svResponse);

			if (!response.isCommitted()) {
				if (request.getAttribute(CLAZZ) == null) {
					request.setAttribute(CLAZZ, CLAZZ);

					if (!setResponse(request, response)) {
						chain.doFilter(request, new DuplicateHeaderWrapper(response));
						setAttribute(request);
					}
					return;
				}

				if (FilterUtil.isView(request)) {
					if (doJspFilter(request, response, chain)) {
						return;
					}
				}
			}
		}

		chain.doFilter(svRequest, svResponse);
	}

	/**
	 * レスポンス設定処理
	 * @param request リクエスト
	 * @param response レスポンス
	 * @return 設定した場合 true を返す。
	 */
	private boolean setResponse(final HttpServletRequest request,
			final HttpServletResponse response) {
		HttpSession session = request.getSession(false);
		if (session != null) {
			String location = getRequestLocation(request);
			synchronized (SessionMutexListener.getMutex(session)) {
				List<String> list = Factory.cast(session.getAttribute(CLAZZ));
				if (FilterUtil.isGetMethod(request.getMethod())) {
					// RAP
					DuplicateBodyResponse dbr = DuplicateBodyResponse.class.cast(
							session.getAttribute(location));
					if (dbr != null) {
						try {
							dbr.copyResponse(response);
							if (0 != dbr.getHeader().getStatus()) {
								session.removeAttribute(location);
								if (list != null) {
									list.remove(location);
								}
							}
						} catch (IOException ex) {
							LogManager.getLogger().info(ex.getMessage());
						}
						return true;
					}
				} else if (FilterUtil.isPostMethod(request.getMethod())) {
					// ダブルクリック用
					if (list != null) {
						for (final String loc : list) {
							DuplicateBodyResponse dbr = DuplicateBodyResponse.class.cast(
											session.getAttribute(loc));
							if (dbr != null && dbr.isSameRequest(location, request)) {
								setRedirect(response, loc);
								return true;
							}
						}
					}
				}
			}
		}
		return false;
	}

	/**
	 * セション設定
	 *
	 * @param request リクエスト
	 */
	private void setAttribute(final HttpServletRequest request) {
		String location = String.class.cast(request.getAttribute(REDIRECT_URL));
		HttpSession session = request.getSession(false);
		if (session != null && location != null) {
			synchronized (SessionMutexListener.getMutex(session)) {
				session.setAttribute(location, request.getAttribute(location));

				CopyOnWriteArrayList<String> list = Factory.cast(session.getAttribute(CLAZZ));
				if (list == null) {
					list = new CopyOnWriteArrayList<>();
				} else {
					list.remove(location);
					while (this.max < list.size()) {
						session.removeAttribute(list.remove(0));
					}
				}
				list.add(location);
				session.setAttribute(CLAZZ, list);
			}
		}
	}

	/**
	 * Jspフィルター処理
	 * @param request リクエスト
	 * @param response レスポンス
	 * @param chain チェイン
	 * @return 処理した場合 true を返す。
	 * @throws IOException IO例外
	 * @throws ServletException サーブレット例外
	 */
	private boolean doJspFilter(final HttpServletRequest request,
			final HttpServletResponse response,
			final FilterChain chain) throws IOException, ServletException {
		HttpSession session = request.getSession(false);
		if (session != null) {
			if (!InterceptResponseWrapper.class.isInstance(response)
					&& DuplicateHeaderWrapper.class.isInstance(response)) {
				DuplicateHeaderWrapper dhw = DuplicateHeaderWrapper.class.cast(response);
				if (FilterUtil.isGetMethod(request.getMethod())) {
					if (needsRedirect(request)) {
						setStatusGet(dhw);
						doFilter(request, dhw, chain, getLocation(request));
						return true;
					}
				} else if (FilterUtil.isPostMethod(request.getMethod())) {
					if (isRedirect(request)) {
						setStatusPost(request, dhw);
						doFilter(request, dhw, chain, getResponsetLocation(request));
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Redirect判断
	 *
	 * @param request リクエスト
	 * @return Redirect時 true を返す。
	 */
	private boolean needsRedirect(final HttpServletRequest request) {
		return SessionAttributeFilter.isSessionAdded(request)
				&& SessionAttributeFilter.isMultipleSession(request);
	}

	/**
	 * ステータス設定
	 * @param dhw レスポンスラッパ
	 */
	private void setStatusGet(final DuplicateHeaderWrapper dhw) {
		if (dhw.getDuplicateHeaderResponse().getStatus() == 0) {
			dhw.setStatus(HttpServletResponse.SC_OK);
		}
	}

	/**
	 * リダイレクト確認
	 * @param request リクエスト
	 * @return リダイレクトの場合 true を返す。
	 */
	private boolean isRedirect(final HttpServletRequest request) {
		return request.getAttribute(NO_REDIRECT) == null;
	}

	/**
	 * ステータス設定
	 * @param request リクエスト
	 * @param dhw レスポンスラッパ
	 */
	private void setStatusPost(final HttpServletRequest request, final DuplicateHeaderWrapper dhw) {
		if (dhw.getDuplicateHeaderResponse().getStatus() == 0) {
			// セション新規開始時
			if (SessionAttributeFilter.isSessionAdded(request)) {
				dhw.setStatus(HttpServletResponse.SC_OK);
				return;
			}
			// セション新規開始時のエラー時
			if (!SessionAttributeFilter.hasSessionAttribute(request)) {
				if (!SessionAttributeFilter.isSessionRemoved(request)) {
					dhw.setStatus(HttpServletResponse.SC_OK);
					return;
				}
			}
			// エラーで同一画面再表示時
			if (RecordURIFilter.getRequestUri(request).equals(
					RecordURIFilter.getResponseUri(request))) {
				dhw.setStatus(HttpServletResponse.SC_OK);
			}
		}
	}

	/**
	 * フィルタ処理
	 * @param request リクエスト
	 * @param dhw レスポンスラッパ
	 * @param chain チェイン
	 * @param location 位置URL
	 * @throws IOException IO例外
	 * @throws ServletException サーブレット例外
	 */
	private void doFilter(final HttpServletRequest request, final DuplicateHeaderWrapper dhw,
			final FilterChain chain, final String location) throws IOException, ServletException {

		InterceptResponseWrapper wrap = new InterceptResponseWrapper(
				dhw, dhw.getDuplicateHeaderResponse());

		chain.doFilter(request, wrap);

		HttpServletResponse response = dhw.getHttpResponse();
		if (!response.isCommitted()) {
			request.setAttribute(REDIRECT_URL, location);
			request.setAttribute(location, getDuplicateResponse(request, wrap));

			setRedirect(response, location);
		}
	}

	/**
	 * レスポンス複製取得
	 * @param request リクエスト
	 * @param wrap レスポンスラッパ
	 * @return レスポンス複製
	 */
	private DuplicateBodyResponse getDuplicateResponse(final HttpServletRequest request,
			final InterceptResponseWrapper wrap) {
		DuplicateBodyResponse dbr = wrap.getDuplicateBodyResponse();
		dbr.setRequestUrl(getRequestLocation(request));
		dbr.setToken(request);
		return dbr;
	}

	/**
	 * リダイレクト設定
	 * @param response レスポンス
	 * @param location URL
	 */
	private void setRedirect(final HttpServletResponse response, final String location) {
		try {
			response.reset();
			response.resetBuffer();
			response.setStatus(HttpServletResponse.SC_SEE_OTHER);
			response.setHeader("Location", response.encodeRedirectURL(location));
			response.setContentLength(0);
			response.flushBuffer();
		} catch (IOException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
	}

	/**
	 * Location取得
	 *
	 * @param request リクエスト
	 * @return Location
	 */
	private String getLocation(final HttpServletRequest request) {
		return RecordURIFilter.getRequestUri(request)
				+ SessionAttributeFilter.toSidQuery(
						RecordURIFilter.getRequestQuery(request), request);
	}

	/**
	 * Location取得
	 *
	 * @param request リクエスト
	 * @return Location
	 */
	private String getRequestLocation(final HttpServletRequest request) {
		String ret = RecordURIFilter.getRequestUri(request);
		String qry = RecordURIFilter.getRequestQuery(request);
		if (!Objects.toString(qry, "").isEmpty()) {
			return ret + "?" + qry;
		}
		return ret;
	}

	/**
	 * Location取得
	 *
	 * @param request リクエスト
	 * @return Location
	 */
	private String getResponsetLocation(final HttpServletRequest request) {
		String uri = RecordURIFilter.getResponseUri(request);
		String qry = RecordURIFilter.getResponseQuery(request);
		if (!Objects.toString(qry, "").isEmpty()) {
			return uri + "?" + qry;
		}
		return uri;
	}

	/**
	 * リダイレクトなし設定
	 * @param request リクエスト
	 */
	public static void setNoRedirect(final HttpServletRequest request) {
		request.setAttribute(NO_REDIRECT, NO_REDIRECT);
	}
}
